
#include "functions.h"


static List *
get_dynamic_rule( RuleEngineContext * r, Obj * rule_name )
{
    List *              it = r->dynamic_rules;
    Id *                kw_name = alloc_id( r->alloc_ctx, "name", 0 );

    // emptyness check
    if( it->car != 0 )
    {
        do
        {
            List *      dyn_rule = (List *)it->car;
            Obj *       name;
            assert( dyn_rule->type == OBJ_LIST );

            name = get_list_elem_by_keyword( dyn_rule, kw_name );
            assert( name != 0 );

            if( 0 == objcmp(name, rule_name) )
            {
                return dyn_rule;
            }

            it = it->cdr;
        }
        while( it != 0 );
    }

    return false;
}


Obj *
f_def_dynamic_rule( void * tag, List * args )
{
    RuleEngineContext * r = tag;

    // keywords for list
    Id *                kw_name     = alloc_id( r->alloc_ctx, "name", 0 );
    Id *                kw_cond     = alloc_id( r->alloc_ctx, "cond", 0 );
    Id *                kw_vars     = alloc_id( r->alloc_ctx, "vars", 0 );
    Id *                kw_action   = alloc_id( r->alloc_ctx, "action", 0 );

    Obj *               name;
    Obj *               cond;
    Obj *               vars;
    Obj *               action;

    // list is empty?
    if( args == 0 )
    {
        evaluator_emit_error( r->eval, QL_ERR_INVALID_ARGUMENTS );
    }

    name    = get_list_elem_by_keyword( args, kw_name );
    cond    = get_list_elem_by_keyword( args, kw_cond );
    vars    = get_list_elem_by_keyword( args, kw_vars );
    action  = get_list_elem_by_keyword( args, kw_action );

    // mandatory dynamic rule arguments are omitted
    if( !name || !cond || !action )
    {
        evaluator_emit_error( r->eval, QL_ERR_INVALID_ARGUMENTS );
    }

    if( get_dynamic_rule(r, name) != 0 )
    {
        // TODO: add suppress flag?
        //return g_nil_obj;
        evaluator_emit_error( r->eval, QL_ERR_RP_DYNAMIC_RULE_EXISTS );
    }
    else
    {
        List *  dyn_rule = list_clone( r->alloc_ctx, args );

        // apply default arguments
        if( !vars )
        {
            list_append( dyn_rule, alloc_listv(r->alloc_ctx, (Obj *)kw_vars, g_nil_obj, 0) );
        }

        // add this rule
        list_push( r->alloc_ctx, r->dynamic_rules, (Obj *)dyn_rule );
    }

    return g_t_obj;
}

Obj *
f_get_dynamic_rule( void * tag, List * args )
{
    RuleEngineContext * r = tag;
    FILE *              fout = stderr;

    if( 0 == args )
    {
        // print all the rules
        return (Obj *)r->dynamic_rules;
    }
    else
    {
        List *  dyn_rule = get_dynamic_rule( r, args->car );
        return (0 != dyn_rule ? (Obj *)dyn_rule : g_nil_obj);
    }
}

static void
reset_vars( RuleEngineContext * r, List * vars_list )
{
    // reset vars
    if( vars_list->car != 0 )
    {
        do
        {
            setvar( r, (Id *)vars_list->car, g_nil_obj );

            vars_list = vars_list->cdr;
        }
        while( 0 != vars_list );
    }
}


static bool
is_var( Obj * elem, List * vars )
{
    if( 0 == elem )
    {
        return false;
    }

    for( ; vars != 0; vars = vars->cdr )
    {
        if( 0 == objcmp(elem, vars->car) )
        {
            return true;
        }
    }

    return false;    
}

static List *
match_static_pattern( RuleEngineContext * r, List * source, List * pattern, List * vars )
{
    bool                result = false;
    List *              new_vars = alloc_list( r->alloc_ctx );

    /*
    List *              s1 = list_clone( r->alloc_ctx, source );
    List *              p1 = list_clone( r->alloc_ctx, pattern );
    */
    
    for( ;; source = source->cdr, pattern = pattern->cdr )
    {
        Obj *           s_obj;
        Obj *           p_obj;

        // come to an end of list
        if( source == 0 || pattern == 0 )
        {
            result = (source == pattern);
            break;
        }

        s_obj = source->car;
        p_obj = pattern->car;
        assert( s_obj->type == OBJ_ID && p_obj->type == OBJ_ID );

        if( s_obj == 0 || p_obj == 0 )
        {
            result = (s_obj == p_obj);
            break;
        }

        if( is_var(p_obj, vars) )
        {
            Obj * o = getvar( r, (Id *)p_obj );
            if( o->type == OBJ_NIL )
            {
                // add newly introduced var
                list_push( r->alloc_ctx, new_vars, p_obj );

                setvar( r, (Id *)p_obj, s_obj );
                continue;
            }

            if( 0 == objcmp(s_obj, o) )
            {
                continue;
            }
            else
            {
                break;
            }
        }

        if( 0 != objcmp(p_obj, s_obj) )
        {
            break;
        }
    }

    /*
    print_obj(
        alloc_listv(r->alloc_ctx, alloc_str(r->alloc_ctx, result ? "match" : "n / m"), 
            s1, p1, new_vars,
            0),
        stderr );
    fputc( '\n', stderr );
    */

    if( !result )
    {
        // free newly introduced vars list
        reset_vars( r, new_vars );
        free_list_chain( r->alloc_ctx, new_vars, 0, 0 );
        return 0;
    }

    return new_vars;
}


static Obj *
match_pattern( RuleEngineContext * r, List * vars, Obj * action, List * patterns_it )
{
    List *  pattern = (List *)patterns_it->car;
    List *  s = r->static_rules;
    Obj *   ret = g_nil_obj;

    //fputs( "match_pattern...\n", stderr );

    if( pattern->type != OBJ_LIST )
    {
        evaluator_emit_error( r->eval, QL_ERR_RP_MALFORMED_DYNAMIC_RULE );
    }

    if( 0 != s->car )
    {
        do
        {
            List *  new_vars = match_static_pattern( r, (List *)s->car, pattern, vars );
            bool    is_empty = false;
            if( 0 != new_vars )
            {
                is_empty = (list_length( new_vars ) == 0);

                if( patterns_it->cdr != 0 )
                {
                    //fputs( "enter recursive match_pattern\n", stderr );
                    ret = match_pattern( r, vars, action, patterns_it->cdr );
                }
                else
                {
                    // here action is to be performed
                    ret = internal_evaluate( r->eval, action );
                }

                reset_vars( r, new_vars );
                free_list_chain( r->alloc_ctx, new_vars, 0, 0 );
            }

            if( is_empty )
            {
                break;
            }

            s = s->cdr;
        }
        while( 0 != s );
    }

    //fputs( "EXIT match_pattern\n", stderr );
    return ret;
}


static Obj *
match_patterns_list( RuleEngineContext * r, List * vars, Obj * action, List * patterns )
{
    reset_vars( r, vars );

    return match_pattern( r, vars, action, patterns );
}



static Obj *
evaluate_rule( RuleEngineContext * r, Obj * cond, List * vars, Obj * action )
{
    switch( cond->type )
    {
    case OBJ_T:
        return internal_evaluate( r->eval, action );

    case OBJ_LIST:
        {
            Id *        kw_and = alloc_id( r->alloc_ctx, "and", r->default_ns );
            List *      full_cond = (List *)cond;
            Obj *       prob_and_elem = list_at( full_cond, 0 );

            if( 0 != prob_and_elem )
            {
                List *  cond_args;
                if( 0 == objcmp(prob_and_elem, (Obj *)kw_and) )
                {
                    cond_args = full_cond->cdr;
                }
                else
                {
                    cond_args = alloc_listv( r->alloc_ctx, (Obj *)full_cond, 0 );
                }

                // check whether conditional arguments exists
                if( cond_args && cond_args->car != 0 )
                {
                    return match_patterns_list(r, vars, action, cond_args);
                }
                else
                {
                    evaluator_emit_error( r->eval, QL_ERR_RP_MALFORMED_DYNAMIC_RULE );
                }
            }
        }
    }
    
    return g_nil_obj;
}


Obj *
f_apply_dynamic_rule( void * tag, List * args )
{
    RuleEngineContext * r = tag;
    List *              rule;

    Id *                kw_cond     = alloc_id( r->alloc_ctx, "cond", 0 );
    Id *                kw_vars     = alloc_id( r->alloc_ctx, "vars", 0 );
    Id *                kw_action   = alloc_id( r->alloc_ctx, "action", 0 );

    Obj *               action;
    Obj *               cond;
    Obj *               vars;

    if( list_length(args) != 1 )
    {
        evaluator_emit_error( r->eval, QL_ERR_INVALID_ARGUMENTS );
    }

    rule = get_dynamic_rule( r, args->car );
    if( 0 == rule )
    {
        evaluator_emit_error( r->eval, QL_ERR_RP_DYNAMIC_RULE_NOT_EXISTS );
    }

    vars = get_list_elem_by_keyword( rule, kw_vars );
    cond = get_list_elem_by_keyword( rule, kw_cond );
    action = get_list_elem_by_keyword( rule, kw_action );

    if( vars->type != OBJ_LIST && vars->type != OBJ_NIL )
    {
        evaluator_emit_error( r->eval, QL_ERR_RP_MALFORMED_DYNAMIC_RULE );
    }

    // all the variables shall be of list type
    if( !list_check_elements_type((List *)vars, OBJ_ID) )
    {
        evaluator_emit_error( r->eval, QL_ERR_FAIL );
    }

    return evaluate_rule( r, cond, (List *)vars, action );
}
